package business

import (
	"context"
	"crypto/sha256"
	"crypto/tls"
	"encoding/base64"
	"fcs-api/src/app/model"
	"fcs-api/src/app/utils"
	"fmt"
	"github.com/minio/minio-go/v7"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

func UploadCode(info model.UploadInfo, token string) error {

	bucketName := info.TargetBucket
	date := time.Now().UTC().Format(http.TimeFormat)
	codeObjectName := info.TargetPath + "/" + info.TargetObject
	checkObjectName := info.TargetPath + "/" + "checksum"
	codeSignedStr := utils.GetSignedStr(bucketName, codeObjectName, "", date, info.Ak, info.Sk, map[string]string{})
	checkSignedStr := utils.GetSignedStr(bucketName, checkObjectName, "", date, info.Ak, info.Sk, map[string]string{})
	codeUrl := model.Base.OssEndpoint + "/" + bucketName + "/" + codeObjectName
	checkUrl := model.Base.OssEndpoint + "/" + bucketName + "/" + checkObjectName

	if info.Code != "" {
		//代码上传到公共库
		decodeBytes, err := base64.StdEncoding.DecodeString(info.Code)
		if err != nil {
			return err
		}
		_, err = model.Base.MinioClient.PutObject(context.Background(), bucketName, codeObjectName, strings.NewReader(string(decodeBytes)), strings.NewReader(string(decodeBytes)).Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"})
		if err != nil {
			logger.Error("UploadCode: oss upload file error, %s", err.Error())
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
		//err = OssUploadOrCopy(codeUrl, date, codeSignedStr, strings.NewReader(string(decodeBytes)), map[string]string{})
		//if err != nil {
		//	logger.Error("UploadCode: oss upload file error, %s", err.Error())
		//	return model.InternalServerError(model.TriggerError, "上传文件失败")
		//}
		//校验和上传到公共库
		checkSum, err := GetCheckSum(strings.NewReader(string(decodeBytes)))
		if err != nil {
			logger.Error("UploadCode: get checksum error, %s", err.Error())
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
		_, err = model.Base.MinioClient.PutObject(context.Background(), bucketName, checkObjectName, strings.NewReader(string(checkSum)), strings.NewReader(string(checkSum)).Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"})
		if err != nil {
			logger.Error("UploadCode: oss upload checksum error, %s", err.Error())
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
		//err = OssUploadOrCopy(checkUrl, date, checkSignedStr, strings.NewReader(checkSum), map[string]string{})
		//if err != nil {
		//	logger.Error("UploadCode: oss upload checksum error, %s", err.Error())
		//	return model.InternalServerError(model.TriggerError, "上传文件失败")
		//}
	}

	if info.SourceBucket != "" { //从oss上传
		ossHost := model.Base.OssAdminHost
		if model.Base.OssUserHost != "" {
			ossHost = model.Base.OssUserHost
		}

		logger.Debug("Bucket: ", info.SourceBucket, "Object: ", info.SourceObject)
		downloadLink, err := GetDownloadLink(ossHost, info.SourceBucket, strings.Replace(info.SourceObject, "/", "", 1), token)
		if err != nil {
			_ = logger.Error("UploadCode：get user file error, %s", err.Error())
			return model.InternalServerError(model.TriggerError, "获取用户文件失败，请检查对象文件路径是否正确")
		}
		logger.Debug("downloadLink: ", downloadLink)
		r, err := http.NewRequest("GET", downloadLink, nil)
		if err != nil {
			_ = logger.Error("handleUploadCode NewRequest error %v", err)
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
		}
		client := &http.Client{Transport: tr}
		resp, err := client.Do(r)
		if err != nil {
			_ = logger.Error("handleUploadCode Client.Do error %v", err)
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
		if resp.StatusCode != 200 {
			body, _ := ioutil.ReadAll(resp.Body)
			_ = logger.Error("handleUploadCode download code error %v : %v ", resp.StatusCode, string(body))
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
		defer func() {
			_ = resp.Body.Close()
		}()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			_ = logger.Error("handleUploadCode ReadAll error %v", err)
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
		checkSum, err := GetCheckSum(strings.NewReader(string(body)))
		if err != nil {
			logger.Error("UploadCode: get checksum error, %s", err.Error())
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}

		//代码上传到公共库
		err = OssUploadOrCopy(codeUrl, date, codeSignedStr, strings.NewReader(string(body)), map[string]string{})
		if err != nil {
			logger.Error("UploadCode: oss upload file error, %s", err.Error())
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}

		//校验和上传到公共库
		err = OssUploadOrCopy(checkUrl, date, checkSignedStr, strings.NewReader(checkSum), map[string]string{})
		if err != nil {
			logger.Error("UploadCode: oss upload checksum error, %s", err.Error())
			return model.InternalServerError(model.TriggerError, "上传文件失败")
		}
	}

	return nil
}

func OssUploadOrCopy(url, date, token string, body io.Reader, ossHeader map[string]string) error {
	header := http.Header{}
	header.Set("date", date)
	hs := utils.NewHeaderSorter(ossHeader)
	for i := range hs.Keys {
		header.Set(hs.Keys[i], hs.Vals[i])
	}
	_, err := utils.HttpReq(url, token, http.MethodPut, body, header)
	if err != nil {
		_ = logger.Error("ossUploadOrCopy Client.Do error %v", err)
		return err
	}
	return nil
}

func GetCheckSum(body io.Reader) (string, error) {

	sha256Handler := sha256.New()
	_, err := io.Copy(sha256Handler, body)
	if err != nil {
		_ = logger.Error("getChecksum, failed to copy code to md5 handler: %v", err)
		return "", err
	}
	checkSum := fmt.Sprintf("%x", sha256Handler.Sum([]byte("")))
	logger.Debug("checkSum: ", checkSum)
	return checkSum, nil
}

func GetDownloadLink(serviceHost, bucketName, objectKey, token string) (string, error) {
	url := serviceHost + fmt.Sprintf("api/v1/buckets/%s/objects/download?prefix=%s", bucketName, base64.StdEncoding.EncodeToString([]byte(objectKey)))
	//body, err := utils.HttpReq(url, token, http.MethodGet, nil, nil)
	//if err != nil {
	//	_ = logger.Error("handleDownloadCode Client.Do error %v", err)
	//	return "", err
	//}
	//codeInfo := &model.CodeInfo{}
	//_ = json.Unmarshal(body, codeInfo)
	//if codeInfo.DownloadLink == "" {
	//	_ = logger.Error("resp err %v", string(body))
	//	return "", errors.New("can not get DownloadLink")
	//}
	return url, nil
}
